/* eslint-disable */
var crc32 = require('crc32'),
    deflate = require('deflate-js'),
    // magic numbers marking this file as GZIP
    ID1 = 0x1F,
    ID2 = 0x8B,
    compressionMethods = {
        'deflate': 8
    },
    possibleFlags = {
        'FTEXT': 0x01,
        'FHCRC': 0x02,
        'FEXTRA': 0x04,
        'FNAME': 0x08,
        'FCOMMENT': 0x10
    },
    osMap = {
        'fat': 0, // FAT file system (DOS, OS/2, NT) + PKZIPW 2.50 VFAT, NTFS
        'amiga': 1, // Amiga
        'vmz': 2, // VMS (VAX or Alpha AXP)
        'unix': 3, // Unix
        'vm/cms': 4, // VM/CMS
        'atari': 5, // Atari
        'hpfs': 6, // HPFS file system (OS/2, NT 3.x)
        'macintosh': 7, // Macintosh
        'z-system': 8, // Z-System
        'cplm': 9, // CP/M
        'tops-20': 10, // TOPS-20
        'ntfs': 11, // NTFS file system (NT)
        'qdos': 12, // SMS/QDOS
        'acorn': 13, // Acorn RISC OS
        'vfat': 14, // VFAT file system (Win95, NT)
        'vms': 15, // MVS (code also taken for PRIMOS)
        'beos': 16, // BeOS (BeBox or PowerMac)
        'tandem': 17, // Tandem/NSK
        'theos': 18 // THEOS
    },
    os = 'unix',
    DEFAULT_LEVEL = 6;

function putByte(n, arr) {
    arr.push(n & 0xFF);
}

// LSB first
function putShort(n, arr) {
    arr.push(n & 0xFF);
    arr.push(n >>> 8);
}

// LSB first
function putLong(n, arr) {
    putShort(n & 0xffff, arr);
    putShort(n >>> 16, arr);
}

function putString(s, arr) {
    var i, len = s.length;
    for (i = 0; i < len; i += 1) {
        putByte(s.charCodeAt(i), arr);
    }
}

function readByte(arr) {
    return arr.shift();
}

function readShort(arr) {
    return arr.shift() | (arr.shift() << 8);
}

function readLong(arr) {
    var n1 = readShort(arr),
        n2 = readShort(arr);

    // JavaScript can't handle bits in the position 32
    // we'll emulate this by removing the left-most bit (if it exists)
    // and add it back in via multiplication, which does work
    if (n2 > 32768) {
        n2 -= 32768;

        return ((n2 << 16) | n1) + 32768 * Math.pow(2, 16);
    }

    return (n2 << 16) | n1;
}

function readString(arr) {
    var charArr = [];

    // turn all bytes into chars until the terminating null
    while (arr[0] !== 0) {
        charArr.push(String.fromCharCode(arr.shift()));
    }

    // throw away terminating null
    arr.shift();

    // join all characters into a cohesive string
    return charArr.join('');
}

/*
 * Reads n number of bytes and return as an array.
 *
 * @param arr- Array of bytes to read from
 * @param n- Number of bytes to read
 */
function readBytes(arr, n) {
    var i, ret = [];
    for (i = 0; i < n; i += 1) {
        ret.push(arr.shift());
    }

    return ret;
}

/*
 * ZIPs a file in GZIP format. The format is as given by the spec, found at:
 * http://www.gzip.org/zlib/rfc-gzip.html
 *
 * Omitted parts in this implementation:
 */
function zip(data, options) {
    var flags = 0,
        level,
        crc, out = [];

    if (!options) {
        options = {};
    }
    level = options.level || DEFAULT_LEVEL;

    if (typeof data === 'string') {
        data = Array.prototype.map.call(data, function (char) {
            return char.charCodeAt(0);
        });
    }

    // magic number marking this file as GZIP
    putByte(ID1, out);
    putByte(ID2, out);

    putByte(compressionMethods['deflate'], out);

    if (options.name) {
        flags |= possibleFlags['FNAME'];
    }

    putByte(flags, out);
    putLong(options.timestamp || parseInt(Date.now() / 1000, 10), out);

    // put deflate args (extra flags)
    if (level === 1) {
        // fastest algorithm
        putByte(4, out);
    } else if (level === 9) {
        // maximum compression (fastest algorithm)
        putByte(2, out);
    } else {
        putByte(0, out);
    }

    // OS identifier
    putByte(osMap[os], out);

    if (options.name) {
        // ignore the directory part
        putString(options.name.substring(options.name.lastIndexOf('/') + 1), out);

        // terminating null
        putByte(0, out);
    }

    deflate.deflate(data, level).forEach(function (byte) {
        putByte(byte, out);
    });

    putLong(parseInt(crc32(data), 16), out);
    putLong(data.length, out);

    return out;
}

function unzip(data, options) {
    // start with a copy of the array
    var arr = Array.prototype.slice.call(data, 0),
        t,
        compressionMethod,
        flags,
        mtime,
        xFlags,
        key,
        os,
        crc,
        size,
        res;

    // check the first two bytes for the magic numbers
    if (readByte(arr) !== ID1 || readByte(arr) !== ID2) {
        throw 'Not a GZIP file';
    }

    t = readByte(arr);
    t = Object.keys(compressionMethods).some(function (key) {
        compressionMethod = key;
        return compressionMethods[key] === t;
    });

    if (!t) {
        throw 'Unsupported compression method';
    }

    flags = readByte(arr);
    mtime = readLong(arr);
    xFlags = readByte(arr);
    t = readByte(arr);
    Object.keys(osMap).some(function (key) {
        if (osMap[key] === t) {
            os = key;
            return true;
        }
    });

    // just throw away the bytes for now
    if (flags & possibleFlags['FEXTRA']) {
        t = readShort(arr);
        readBytes(arr, t);
    }

    // just throw away for now
    if (flags & possibleFlags['FNAME']) {
        readString(arr);
    }

    // just throw away for now
    if (flags & possibleFlags['FCOMMENT']) {
        readString(arr);
    }

    // just throw away for now
    if (flags & possibleFlags['FHCRC']) {
        readShort(arr);
    }

    if (compressionMethod === 'deflate') {
        // give deflate everything but the last 8 bytes
        // the last 8 bytes are for the CRC32 checksum and filesize
        res = deflate.inflate(arr.splice(0, arr.length));
    }


    return res;
}
export default {
    zip: zip,
    unzip: unzip,
    get DEFAULT_LEVEL() {
        return DEFAULT_LEVEL;
    }
};
